package org.haptimap.hcimodules.guiding;

import org.haptimap.HaptiMap;
import org.haptimap.ToolkitService;
import org.haptimap.ToolkitService.ToolkitBinder;
import org.haptimap.hcimodules.HCIModule;
import org.haptimap.hcimodules.util.MagneticCompass;
import org.haptimap.hcimodules.util.MyLocationModule;
import org.haptimap.hcimodules.util.MyLocationModule.MyLocationListener;
import org.haptimap.hcimodules.util.WayPoint;

import android.Manifest.permission;
import android.app.Activity;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.location.Location;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Vibrator;
import android.util.Log;


/**
 * <p>
 * A class/module which provides vibration according to a certain pattern,
 * depending on the angle the user is currently pointing to and whether the distance feedback is enabled.
 * </p>
 * 
 * <p>
 * The current angle for the max deviation is <strong>60 degrees</strong> and within this range the module will give a 
 * feedback. If the user points to an angle that exceeds a +(-) 60 degrees, no feedback will be given.
 * </p>
 * 
 * <p>
 * The class can also be used together with a {@link HapticGuideEventListener} which will
 * be called on different events. 
 * 
 * <p>When a destination is reached, besides a broadcast which can be received with a 
 * {@link BroadcastReceiver} and registered with the action {@link #ACTION_DESTINATION_REACHED},
 * the HapticGuide will also call an {@link HapticGuideEventListener} event and vibrate with 
 * a specific pattern. The module will then go into a pause state.
 * 
 * <p>The module follows a state machine, which can be compared to the regular Android approach. The
 * states that included are: {@link #onStart()} -> {@link #onResume()} -> {@link #onPause()} -> {@link #onStop()} 
 * and {@link #onDestroy()}
 * 
 * <p>For further details about the different states, please read the {@link HCIModule}. 
 * 
 * <p>The simplest way in using this module is by <strong>creating</strong> this class with the {@link #HapticGuide(Context, WayPoint)}
 * constructor with no {@link #HAPTIMAP_TOOLKIT_ENABLED} and with the <strong>first destination</strong> to get guided to. 
 * The interaction does not begin until the device gets a GPS fix. Once the <strong>destination is reached</strong> the 
 * {@link HapticGuideEventListener} calls the {@link HapticGuideEventListener#onDestinationReached(long[])}
 * and <strong>a destination pattern will be emitted</strong>. Then you can send a new destination with one of the 
 * <strong>setNextDestination(...)</strong> methods.
 * </p>
 *  
 *  <p>The recommendation in using this module is by using the default constructor and by following the Android's
 *  {@link Activity} approach when moving through the different states. It is recommended to start the module with
 *  the {@link #onStart()} followed by the setNextDestination(...)
 *  </p>
 *  
 * <p>This module <strong>must</strong> have the following permissions in the AndroidManifest.xml file:
 * <ul>
 * <li>{@link permission#VIBRATE}</li> 
 * <li>{@link permission#ACCESS_FINE_LOCATION}</li>
 * <li>{@link permission#ACCESS_NETWORK_STATE}</li> If network access is used
 * <li>{@link permission#WRITE_EXTERNAL_STORAGE}</li> If {@link #HAPTIMAP_TOOLKIT_ENABLED} flag is used.
 * </ul>
 * </p>
 * 
 * <p>The following value must exist in the project's Manifest file as an intent filter for the service
 * to work:
 * <pre>
 *  &lt;service android:name="org.haptimap.ToolkitService"&gt;
 *   &lt;intent-filter&gt;
 *     &lt;action android:name="org.haptimap.ToolkitService.SERVICE" /&gt;
 *   &lt;/intent-filter&gt;
 *  &lt;/service&gt;
 * </pre>       	
 * 
 * <p>If any constructor without valid flags is used, the default values are:
 * <ul>
 * <li>{@link #DISTANCE_FEEDBACK_ENABLED} = <strong>true</strong></li>
 * <li>{@link #HAPTIMAP_TOOLKIT_ENABLED} = <strong>false</strong></li>
 * <li>{@link #DEFAULT_DISTANCE_LIMIT} = <strong>300 meter</strong></li>
 * </ul>
 * </p>
 * 
 * @see ToolkitService
 * @see HaptiMap
 * @see ToolkitBinder
 * 
 * @author Miguel Molina, July 2011
 */
public class HapticGuide extends HCIModule{

	protected static final String TAG = "HapticGuide";

	/**
	 * Used for debug purposes. Enable it for output on DDMS generated by the Log.e(... 
	 * The default value is true. If disabled, the regular errors from exceptions will still be visible. 
	 */
	private static final boolean DEBUG = false;

	/**
	 * Broadcast action: Indicates when the destination is reached
	 * 
	 * <p>Sent when the user arrived to the destination. The current radius
	 * to the POI is 15 meters.
	 */
	public static final String ACTION_DESTINATION_REACHED = "org.haptimap.geiger.action.DESTINATION_REACHED";

	/**
	 * Flag indicating whether this module should encode the vibration feedback
	 * using the distance between the user and the POI 
	 * 
	 * @see #setDistanceFeedbackEnabled(boolean)
	 * @see #isDistanceFeedbackEnabled()
	 */
	public static final int DISTANCE_FEEDBACK_ENABLED = 0x1000;

	/**
	 * Flag indicating whether this module should use the Haptimap {@link ToolkitService}
	 * 
	 * @see #setHaptiMapToolkitEnabled(boolean)
	 * @see #isHaptiMapToolkitEnabled()
	 */
	public static final int HAPTIMAP_TOOLKIT_ENABLED = 0x2000;

	/**
	 * Constants used by the {@link HapticGuideEventListener} listener
	 */
	private static final int ON_DESTINATION_REACHED = 1;
	/**
	 * Constants used by the {@link HapticGuideEventListener} listener
	 */	
	private static final int ON_PREPARED = 2;
	/**
	 * Constants used by the {@link HapticGuideEventListener} listener
	 */
	private static final int ON_RATE_INTERVALL_CHANGED = 3;

	/**
	 * Pattern used for vibration when the user is pointing at a POI within  
	 * +(-) 60 degrees.
	 */
	private static final long[] AHEAD_PATTERN = {0, 1, 25, 45, 100, 1, 25, 55, 120, 1, 25, 65};

	/**
	 * Pattern used for vibration when the user has arrived to a POI +(-) {@value #POI_RADIUS} meters
	 */
	protected static final long[] DESTINATION_REACHED = {0, 50, 100, 500, 100, 50};

	/**
	 * Value used when the interaction does not require a new value for vibration.
	 * It does happen if the {@link #DISTANCE_FEEDBACK_ENABLED} is enabled and the 
	 * interaction takes longer than the default value
	 * {@link #DEFAULT_VIBRATION_RATE_INTERVAL}
	 */
	protected static final long[] ZERO = {0};

	/**
	 * The default value for the distance limit in meters. Within this range and if the 
	 * {@link #DISTANCE_FEEDBACK_ENABLED} is set/enabled, the pattern will be encoded also
	 * using the distance as a parameter. When the distance is exceeded, the distance
	 * will still remain as the {@link #DEFAULT_DISTANCE_LIMIT} ({@value #DEFAULT_DISTANCE_LIMIT} meters)
	 * 
	 * <p>The default value can be changed using {@link #setDistanceLimit(int)}
	 * By changing the default value, the max distance used for encoding the pattern will also
	 * change to the set value.
	 * 
	 * @see {@link #setDistanceLimit(int)}
	 */
	public static final int DEFAULT_DISTANCE_LIMIT = 300;

	/**
	 * The current value for the size of the POI in meters. The value is the radius to the POI's center
	 * and will act as an argument when deciding when the user has "arrived" to the destination.
	 * <p>When the user has arrived and the {@link HapticGuideEventListener} is registered, an event
	 * will be called.
	 * 
	 * @see #ACTION_DESTINATION_REACHED
	 * @see {@link HapticGuideEventListener#onDestinationReached(long[])}
	 */
	private static final int POI_RADIUS = 15;

	/**
	 * Use with {@link #initHaptiMapToolkit()} to bind to the {@link ToolkitService}
	 * using an {@link Intent}
	 * <p>The value must exist in the project's Manifest file as an intent filter for the service
	 * to work:
	 * 
	 *  <pre>
	 *  &lt;service android:name="org.haptimap.ToolkitService"&gt;
	 *   &lt;intent-filter&gt;
	 *     &lt;action android:name="org.haptimap.ToolkitService.SERVICE" /&gt;
	 *   &lt;/intent-filter&gt;
	 *  &lt;/service&gt;
	 *</pre>       	 
	 */
	private static final String HAPTIMAP_TOOLKIT_SERVICE = "org.haptimap.ToolkitService.SERVICE";

	/**
	 * Default repeat value for the vibrator.
	 */
	private static final int NO_REPEAT = -1;

	/**
	 * The default value for the max deviation in degrees from target. The current value is for
	 * either sides from the target, meaning +(-) {@value #MAX_AHEAD_DEVIATION}  
	 */
	private static final int MAX_AHEAD_DEVIATION = 30;

	/**
	 * The default vibration rate. This is the value the internal handler will use for interaction. 
	 */
	private static final int DEFAULT_VIBRATION_RATE_INTERVAL = 500;

	/**
	 * The default value for vibration if the maximal distance is larger than the default value
	 * {@link #DEFAULT_DISTANCE_LIMIT} or the value set by {@link #setDistanceLimit(int)}
	 * 
	 *  @see #setDistanceLimit(int)
	 */
	private static final int OUT_OF_RANGE_PERIOD = 2000;

	/**
	 * The minimal length in milliseconds between vibrations due to the length of the vibration pattern.
	 */
	private static final int MIN_PATTERN_LENGTH_TIME = 470;

	private int mCurrentDistanceLimit = DEFAULT_DISTANCE_LIMIT;
	private HapticGuideEventListener mHapticGuideEventListener;
	private WayPoint mDestination;
	private Location mCurrentLocation;
	private Vibrator mVibrator;
	private boolean vibrationRunnerStarted = false;

	//Haptimap values
	private HaptiMap mHaptiMap;
	private boolean mServiceBound;
	private int mLocationSensorId;
	private double[] mHaptiMapLocation = new double[3];

	//Current values used for location and the compass.
	private MyLocationModule mMyLocation;
	private MagneticCompass mCompass;

	//The value in meters between the different zones for where the distances make differences
	//on the rate.
	private static final int RED_ZONE = 50;
	private static final int YELLOW_ZONE = 75;
	private static final int GREEN_ZONE = 100;
	private static final int OUT_OF_BOUNDS_ZONE = 300;

	//Factors that direct affect he rate values.
	private static final int RED_ZONE_FACTOR = 100;
	private static final int YELLOW_ZONE_FACTOR = 150;
	private static final int GREEN_ZONE_FACTOR = 200;
	private static final int OUT_OF_BOUNDS_ZONE_FACTOR = 300;

	private static final int AHEAD_ZONE_STATE = 1;
	private static final int TURN_AROUND_STATE = 4;

	//Values needed when creating the vibration pattern
	private int mCurrentState;
	private int mPrevState;
	private float mCurrentPeriod = 0f;

	private int mPatternLength;
	private long[] mPattern = ZERO;
	private boolean mAhead;

	private float mDeviation = 0f;
	private float mNextDirection = 0f;
	private float mAzimuth = 0f;
	private int mDistanceToDestination;
	//The module's current flags.
	//Flags containing values for the current settings.
	private int mCurrentFlags;

	//Internal handler that takes care of the vibration runnable.
	private Handler mHandler = new Handler();
	//How often the handler will postdelay the runner
	private int mCurrentPostDelayValue = DEFAULT_VIBRATION_RATE_INTERVAL;
	private int mCustomPostDelayValue = DEFAULT_VIBRATION_RATE_INTERVAL;
	private boolean mHasCustomPostDelayValue = false;

	/**
	 * Default constructor. The context expected is an {@link Activity#getApplicationContext()}
	 * When using this constructor, no destination is set and the module will not start until 
	 * a destination is set. A destination POI may be set by using one of the 
	 * {@link #setNextDestination(String, String, String)} or one of the other setNextDestination methods.
	 * 
	 * <p>The following values are set by default: 
	 * 
	 * <ul>
	 * <li>{@link #DISTANCE_FEEDBACK_ENABLED}= true
	 * <li>{@link #HAPTIMAP_TOOLKIT_ENABLED}= false
	 * <li>{@link #DEFAULT_DISTANCE_LIMIT} = 300 meter
	 * </ul>
	 * 
	 * @param context The application context 
	 * 
	 * @see #setNextDestination(String, String, String)
	 * @see #setNextDestination(String, double[])
	 * @see #setDistanceFeedbackEnabled(boolean)
	 * @see #setHaptiMapToolkitEnabled(boolean)
	 */
	public HapticGuide(Context context) {
		super(context);
		mServiceBound = false;
		mLocationSensorId = -1;
		this.mDestination = null;
		setFlag(DISTANCE_FEEDBACK_ENABLED, true);
		Log.i(TAG, "Module created"); 
	}


	/**
	 * Default constructor. The context expected is an {@link Activity#getApplicationContext()}
	 * and the destination is the first or only point to where the user will be guided to.
	 * 
	 * <p>The following values are set by default: 
	 * 
	 * <ul>
	 * <li>{@link #DISTANCE_FEEDBACK_ENABLED}= true
	 * <li>{@link #HAPTIMAP_TOOLKIT_ENABLED}= false
	 * <li>{@link #DEFAULT_DISTANCE_LIMIT} = 300 meter
	 * </ul>
	 * 
	 * @param context The application context 
	 * @param destination The {@link WayPoint} destination to the first POI
	 * 
	 * @see #setDistanceFeedbackEnabled(boolean)
	 * @see #setHaptiMapToolkitEnabled(boolean)
	 * 
	 * @throws IllegalArgumentException if destination is null
	 */
	public HapticGuide(Context context, WayPoint destination){
		this(context);
		if(destination == null){
			throw new IllegalArgumentException("destination is null");
		}
		this.mDestination = destination;
	}

	/**
	 * Default constructor. The context expected is an {@link Activity#getApplicationContext()},
	 * the destination is the first or only point to where the user will be guided to and an 
	 * array with values for the flags. 
	 * 
	 * <p>Available flags are:
	 * <ul> 
	 * <li>{@link #DISTANCE_FEEDBACK_ENABLED}
	 * <li>{@link #HAPTIMAP_TOOLKIT_ENABLED}
	 * </ul>
	 * 
	 * @param context The application's context
	 * @param flags An array with flags to enable in this module
	 * @param destination The destination {@link WayPoint}
	 * 
	 * @throws IllegalArgumentException if the flags array is null or has length < 1
	 */
	public HapticGuide(Context context, int[] flags, WayPoint destination){
		this(context, destination);
		if (flags == null || flags.length < 1) {
			throw new IllegalArgumentException("flags is null or has length < 1");
		}		
		for(int i = 0; i < flags.length; i++){
			setFlag(flags[i], true);
		}
		if(DEBUG){
			Log.e(TAG, "HapticGuide(context, currentFlags, destination) gives currentFlags=" + isDistanceFeedbackEnabled() + " this.waypoint == null:" + (this.mDestination == null));
		}
	}

	/**
	 * The context expected is an {@link Activity#getApplicationContext()},
	 * the destination is the first point or the only point to where the user will be guided to. The 
	 * array with values for the flags might be null. If the flags are <strong>null</strong>, the default 
	 * values will the be as follows:
	 * 
	 * <ul>
	 * <li>{@link #DISTANCE_FEEDBACK_ENABLED} = true
	 * <li>{@link #HAPTIMAP_TOOLKIT_ENABLED} = false
	 * <li>{@link #DEFAULT_DISTANCE_LIMIT} = 300 meter
	 * </ul>
	 * 
	 * @param context The application's context
	 * @param flags An array with flags to enable in this module. The value can be <strong>null</strong>
	 * @param destinationName The name of the destination
	 * @param latitude The latitude in WGS84 format.
	 * @param longitude The longitude in WGS84 format.
	 */
	public HapticGuide(Context context, int[] flags, String destinationName, String latitude, String longitude){
		this(context);
		this.mDestination = new WayPoint(destinationName, latitude, longitude);
		if(flags == null || flags.length < 1){
			setFlag(DISTANCE_FEEDBACK_ENABLED, true);
			setFlag(HAPTIMAP_TOOLKIT_ENABLED, false);
		} else {
			for(int i = 0; i < flags.length; i++){
				setFlag(flags[i], true);
			}
		}
	}

	@Override
	public void onStart() {
		startMyLocation();
		startVibrator();
		startCompass();
		startHandler();
		if(DEBUG) Log.d(TAG, "onStart");
	}

	@Override
	public void onPause() {
		pauseHandler();
		releaseVibrator();
		pauseCompass();
		pauseMyLocation();
		if(DEBUG) Log.d(TAG, "onPause");
	}


	@Override
	public void onResume() {
		resumeHandler();
		startVibrator();
		resumeCompass();
		resumeMyLocation();
		if(DEBUG) Log.d(TAG, "onResume");
	}

	@Override
	public void onStop() {
		stopHandler();
		releaseVibrator();
		stopCompass();
		stopMyLocation();
		if(DEBUG) Log.d(TAG, "onStop");
	}

	@Override
	public void onDestroy() {
		stopHandler();
		releaseVibrator();		
		listenerHandler = null;
		destroyMyLocation();
		destroyCompass();
		if(DEBUG) Log.d(TAG, "onDestroy");
	}

	/**
	 * @return whether this module has a distance feedback enabled. 
	 * @see #setDistanceFeedbackEnabled(boolean)
	 */
	public boolean isDistanceFeedbackEnabled(){
		return DISTANCE_FEEDBACK_ENABLED == (mCurrentFlags & DISTANCE_FEEDBACK_ENABLED);
	}

	/**
	 * Set whether this module does care about encoding the distance to the target.
	 * If this value is enabled, the module will be aware of the {@link #setDistanceLimit(int)}
	 * value.
	 * @param distanceFeedbackEnabled whether the distance feedback is enabled.
	 * @see #setDistanceLimit(int)
	 */
	public void setDistanceFeedbackEnabled(boolean distanceFeedbackEnabled){
		setFlag(DISTANCE_FEEDBACK_ENABLED, distanceFeedbackEnabled);
	}

	/**
	 * @return whether the Haptimap {@link ToolkitService} is enabled
	 *  
	 * @see #setHaptiMapToolkitEnabled(boolean)
	 */
	public boolean isHaptiMapToolkitEnabled(){
		return HAPTIMAP_TOOLKIT_ENABLED == (mCurrentFlags & HAPTIMAP_TOOLKIT_ENABLED);
	}

	/**
	 * Set whether the {@link ToolkitService} is enabled. 
	 * 
	 * @param haptiMapToolkitEnabled whether the {@link #HAPTIMAP_TOOLKIT_ENABLED} is enabled.
	 * 
	 * @see #isHaptiMapToolkitEnabled()
	 */
	public void setHaptiMapToolkitEnabled(boolean haptiMapToolkitEnabled){
		setFlag(HAPTIMAP_TOOLKIT_ENABLED, haptiMapToolkitEnabled);
	}

	/**
	 * The flag used to control the behavior of this module
	 * 
	 * @param flag Constant with value to be used 
	 * @param enabled Whether the value is enabled
	 * 
	 * @see {@link #DISTANCE_FEEDBACK_ENABLED}
	 * @see {@link #HAPTIMAP_TOOLKIT_ENABLED}
	 */
	private void setFlag(int flag, boolean enabled){
		if((flag & DISTANCE_FEEDBACK_ENABLED) == DISTANCE_FEEDBACK_ENABLED){
			if(DEBUG) Log.i(TAG, "DISTANCE_FEEDBACK_ENABLED=" + enabled);
			if(enabled){
				mCurrentFlags |= DISTANCE_FEEDBACK_ENABLED;
			} else {
				mCurrentFlags &= ~DISTANCE_FEEDBACK_ENABLED;
				mCurrentPostDelayValue = DEFAULT_VIBRATION_RATE_INTERVAL;
			}

		} else if((flag & HAPTIMAP_TOOLKIT_ENABLED) == HAPTIMAP_TOOLKIT_ENABLED){
			if(DEBUG) Log.i(TAG, "HAPTIMAP_TOOLKIT_ENABLED=" + enabled);
			if(enabled){
				mCurrentFlags |= HAPTIMAP_TOOLKIT_ENABLED;

			} else {
				mCurrentFlags &= ~HAPTIMAP_TOOLKIT_ENABLED;
				if(mServiceBound) unbindToolkitService();
			}
			stopMyLocation();
			startMyLocation();
		}
	}

	/**
	 * Set the limit in meters for this module. The default value is {@value #DEFAULT_DISTANCE_LIMIT} meters.
	 * When the limit is reached and the {@link #DISTANCE_FEEDBACK_ENABLED} is enabled,
	 * the feedback will not change for distance larger than the limit set as a distance limit.
	 * 
	 * @param distanceLimit The distance in meters to the next destination 
	 * for this module to make changes on. If the destination's position is above the limit, 
	 * the feedback remains with the same values as with this maximum. 
	 */
	public void setDistanceLimit(int distanceLimit){
		this.mCurrentDistanceLimit = distanceLimit;
	}

	/**
	 * Initialize the Compass {@link MagneticCompass}, which is a {@link HCIModule}
	 * @see {@link MagneticCompass} for more details.
	 */
	private void startCompass() {
		mCompass = new MagneticCompass(context);
		mCompass.setOnCompassListener(new MagneticCompass.CompassListener() {

			public void onCompassChanged(float[] values) {
				HapticGuide.this.mAzimuth = values[0];
			}
		});
		mCompass.onStart();
	}	

	private void pauseCompass() {
		if(mCompass != null){
			mCompass.onPause();	
		}
	}

	private void resumeCompass(){
		if(mCompass != null){
			mCompass.onResume();
		}
	}

	private void stopCompass(){
		if(mCompass != null){
			mCompass.onStop();
		}
	}	

	private void destroyCompass() {
		if(mCompass != null){
			mCompass.onDestroy();
		}
	}	

	/**
	 * If the {@link #HAPTIMAP_TOOLKIT_ENABLED} value is enabled, this module will bind to 
	 * the ToolkitService, which provides an intent of the {@link HaptiMap} class.
	 * 
	 * @see {@link HaptiMap} for more details.
	 */
	private synchronized void bindToToolkitService() {
		Intent hmService = new Intent(HAPTIMAP_TOOLKIT_SERVICE);
		mServiceBound = context.bindService(hmService, hmServiceConnection, Context.BIND_AUTO_CREATE);
		context.startService(hmService);
		String started = mServiceBound ? "started" : "not started!";
		onPrepared(mServiceBound);
		if(DEBUG){
			Log.d(TAG, "Haptimap Service " + started);
		}
		if(mServiceBound){
			try{
				initHaptiMapToolkit();
			} catch (Exception exc){
				Log.e(TAG, exc.toString());
			}
		}
	}

	/**
	 * Initialized the Android System's {@link Vibrator}
	 */
	private void startVibrator() {
		if(mVibrator == null){
			mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
			if(DEBUG) Log.d(TAG, "Vibrator initialized");
		}
	}

	/**
	 * Initialize and starts the {@link MyLocationModule} HCIModule or the {@link ToolkitService} depending on 
	 * the {@link #HAPTIMAP_TOOLKIT_ENABLED} flag.
	 * 
	 * <p>A {@link MyLocationListener} locationlistener is also initialized and is called when the location changes
	 * if the {@link MyLocationModule} is initialized.
	 * 
	 * @see {@link MyLocationListener} for more details.
	 */
	private void startMyLocation(){	
		if(isHaptiMapToolkitEnabled()){
			if(!mServiceBound){
				bindToToolkitService();
				updateMyHaptiMapLocation();
			}
		} else {
			mMyLocation = new MyLocationModule(context);
			mMyLocation.setOnMyLocationListener(new MyLocationListener() {

				public void onMyLocationChanged(Location location) {
					if(location != null){

						HapticGuide.this.mCurrentLocation = location;
						if(DEBUG) Log.d(TAG, mCurrentLocation.toString());
					}
				}
			});
			mMyLocation.onStart();
		}		
	}

	private void pauseMyLocation(){
		if(mMyLocation != null){
			mMyLocation.onPause();
		}
	}	

	private void resumeMyLocation(){
		if(isHaptiMapToolkitEnabled()){
			if(!mServiceBound){
				bindToToolkitService();
			}
		} else {
			if(mMyLocation != null){
				mMyLocation.onResume();
			}
		}
	}	

	private void stopMyLocation() {
		if(isHaptiMapToolkitEnabled()){
			unbindToolkitService();
		} else {
			if(mMyLocation != null){
				mMyLocation.onStop();
			}
		}
		mCurrentLocation = null;
		mMyLocation.unregisterListener();
	}

	private void destroyMyLocation(){
		if(isHaptiMapToolkitEnabled()){
			if(mServiceBound){
				unbindToolkitService();
				hmServiceConnection = null;
				stopToolkitService();
			}
		} else {
			if(mMyLocation != null){
				mMyLocation.unregisterListener();
				mMyLocation.onDestroy();
			}
		}
		mCurrentLocation = null;
	}

	/**
	 * Releases the {@link Vibrator} when necessary. If the Manifest xml file does not
	 * fullfill the Vibration security, then an exception is thrown.
	 * 
	 * @throws SecurityException Exception if the attribute android.permission.VIBRATE
	 * does not exist in the Manifest file.
	 */
	private void releaseVibrator() throws SecurityException{
		if(mVibrator != null){
			mVibrator.cancel();
		}
		mVibrator = null;
	}

	/**
	 * Private method used to unbind the {@link ToolkitBinder} from this module
	 */
	private void unbindToolkitService() {
		if(mLocationSensorId >= 0) {
			if(mHaptiMap.LocationSensorUninitialise(mLocationSensorId) != HaptiMap.SUCCESS) {
				Log.e(TAG, "Unable to uninitialise location sensor");
				return;
			}
			mLocationSensorId = -1;
		} else if(mHaptiMap != null) {
			unbindService();
			mHaptiMap = null;
		} else {
			Log.e(TAG, "Cannot unbind - service not bound");
		}
	}

	/**
	 * This method is called when the module is about to release all its resources.
	 * The module unbinds from the Haptimap toolkit service and sends an {@link #onPrepared(boolean)}
	 * when finished.
	 */
	private void unbindService(){
		if(mServiceBound){
			context.unbindService(hmServiceConnection);
			mServiceBound = false;
			onPrepared(mServiceBound);
			if(DEBUG) Log.d(TAG, "Haptimap Service unbound");
		}
	}

	/**
	 * Besides unbinding from the toolkitservice, the module can also stop the ToolkitService.
	 * This is necesary if this is the last module that leaves the service and wish to stop it from running.
	 * If there are more modules calling or registered on the {@link ToolkitService} no action
	 * is performed.
	 * 
	 * @see {@link Service}
	 */
	private void stopToolkitService(){
		boolean ans = context.stopService(new Intent(HAPTIMAP_TOOLKIT_SERVICE));
		if (DEBUG) Log.d(TAG, "The service is " + (ans ? "stopped" : "still running"));
	}

	/**
	 * A {@link ServiceConnection} giving the access to the {@link ToolkitBinder} 
	 * provided by the {@link ToolkitService}
	 * 
	 * @see {@link ToolkitService}
	 */
	private ServiceConnection hmServiceConnection = new ServiceConnection() {

		public void onServiceDisconnected(ComponentName name) {
			mHaptiMap = null;
		}

		public void onServiceConnected(ComponentName name, IBinder service) {
			mHaptiMap = ((ToolkitService.ToolkitBinder)service).getHaptiMap();
			if(mHaptiMap != null){
				try{
					initHaptiMapToolkit();
				}catch (Exception e){
					Log.e(TAG, e.toString());
				}
			}
		}
	};

	/**
	 * Return a pattern containing the values used for vibration when a user arrives to 
	 * a destination.
	 * 
	 * @return Array containing the values used for vibration by the module when 
	 * a user arrives to a POI 
	 */
	public long[] getDestinationReachedPattern(){
		return DESTINATION_REACHED;
	}

	/**
	 * The module vibrate according to the pattern generated by {@link #getPattern(float, int)}
	 * If the {@link Vibrator} is null, nothing but an output on the DDMS happens.
	 * 
	 * @param pattern The pattern containing the values needed to create a vibration feedback.
	 * The default behavior is NO_REPEAT, which means that the vibrator will vibrate once with 
	 * the current pattern.
	 */
	private void vibrate(long[] pattern){
		if(mVibrator != null){
			mVibrator.vibrate(pattern, NO_REPEAT);
		} else {
			Log.e(TAG, "Vibrator not initialized");
		} 
	}

	/**
	 * Registers a listener {@link HapticGuideEventListener} which is called on various events.
	 * 
	 * @param hapticGuideEventListener The listener that will be registered for callbacks.
	 * 
	 * @throws IllegalArgumentException
	 * 
	 * @see {@link HapticGuideEventListener} for more details.
	 */
	public void registerHapticGuideEventListener(HapticGuideEventListener hapticGuideEventListener){
		if(hapticGuideEventListener == null){
			throw new IllegalArgumentException("hapticGuideEventListener==null");
		}
		this.mHapticGuideEventListener = hapticGuideEventListener;
	}

	/**
	 * Unregister the Listener, if it has been registered before, and no more callbacks will
	 * be sent.
	 * 
	 * @see {@link #registerHapticGuideEventListener(HapticGuideEventListener)}
	 */
	public void unregisterHapticGuideEventListener(){
		this.mHapticGuideEventListener = null;
	}

	/**
	 * 
	 * @return an array with one element with value zero.
	 */
	protected long[] getZeroPattern(){
		return ZERO;
	}	

	/**
	 * When a POI is reached, the {@link HapticGuide} will send a message containing 
	 * information about this event. The event is then processed by a {@link Handler}
	 * and an action broadcast is sent. If there is any listener registered in the 
	 * module, it will also receive a callback. 
	 * 
	 * @see {@link #ACTION_DESTINATION_REACHED}
	 * @see {@link #registerHapticGuideEventListener(HapticGuideEventListener)}
	 */
	private void onDestinationReached(){
		Message msg = Message.obtain();
		msg.what = ON_DESTINATION_REACHED;
		msg.obj = DESTINATION_REACHED;
		listenerHandler.sendMessage(msg);		
		context.sendBroadcast(new Intent(ACTION_DESTINATION_REACHED));
		vibrate(DESTINATION_REACHED);
	}

	/**
	 * This event is sent if there are any HapticGuideEventListener registered. 
	 * @param prepared Whether the toolkit is successfully registered/unregistered
	 * 
	 * @see {@link #registerHapticGuideEventListener(HapticGuideEventListener)}
	 */
	private void onPrepared(boolean prepared){
		Message msg = Message.obtain();
		msg.what = ON_PREPARED;
		msg.obj = prepared;
		listenerHandler.sendMessage(msg);
	}

	/**
	 * This event is called giving the interval rate between vibration. 
	 * The default value is {@link #DEFAULT_VIBRATION_RATE_INTERVAL} and that 
	 * is the minimal time if the {@link #DISTANCE_FEEDBACK_ENABLED} 
	 * is not enabled. If enabled, the next vibration time interaction varies
	 * depending on the users position to the POI they are getting to.
	 *    
	 * @param rate The value before the next interaction occurs.
	 * 
	 * @see {@link #registerHapticGuideEventListener(HapticGuideEventListener)}
	 * @see {@link #setDistanceFeedbackEnabled(boolean)}
	 */
	private void onRateIntervallChanged(int rate){
		Message msg = Message.obtain();
		msg.what = ON_RATE_INTERVALL_CHANGED;
		msg.obj = rate;
		listenerHandler.sendMessage(msg);
	}

	/**
	 * {@link Handler} taking care of the internal listener.
	 * The messages will be executed if the {@link HapticGuideEventListener} is registered. 
	 * 
	 * @see {@link #registerHapticGuideEventListener(HapticGuideEventListener)}
	 */
	private Handler listenerHandler = new Handler(){
		public void handleMessage(android.os.Message msg) {

			if(mHapticGuideEventListener != null){
				switch(msg.what){

				case ON_DESTINATION_REACHED:
					mHapticGuideEventListener.onDestinationReached((long[]) msg.obj);
					listenerHandler.postDelayed(new Runnable() {

						public void run() {
							HapticGuide.this.onPause();							
						}
					}, 1000);
					break;

				case ON_PREPARED:
					mHapticGuideEventListener.onPrepared((Boolean) msg.obj);
					break;

				case ON_RATE_INTERVALL_CHANGED:
					mHapticGuideEventListener.onRateIntervalChanged((Integer) msg.obj);
					break;

				default:
					throw new IllegalArgumentException("Value does not exist");
				}
			}
		};
	};

	/**
	 * Set the next destination. This is the point to where the user will be guided to 
	 * @param destinationName The name of the destination
	 * @param latitude The latitude in WSG84 decimal format
	 * @param longitude The longitude in WSG84 decimal format
	 * 
	 * @see {@link WayPoint}
	 * @see #setNextDestination(WayPoint)
	 * @see #setNextDestination(String, Location)
	 * @see #setNextDestination(String, double[])
	 */
	public void setNextDestination(String destinationName, String latitude, String longitude) {
		this.mDestination = new WayPoint(destinationName, latitude, longitude);
		this.onResume();
	}

	/**
	 * Set the next destination. This is the point to where the user will be guided to 
	 * @param destinationName The name of the destination
	 * @param destination The {@link Location} destination
	 * 
	 * @see {@link WayPoint}
	 * @see #setNextDestination(WayPoint)
	 * @see #setNextDestination(String, double[])
	 * @see #setNextDestination(String, String, String)
	 */
	public void setNextDestination(String destinationName, Location destination){
		this.mDestination = new WayPoint(destinationName, destination);
		this.onResume();
	}

	/**
	 * Set the next destination. This is the point to where the user will be guided to
	 * @param destinationName The name of the destination
	 * @param latlon An array containing the latitude and the longitude as follows:
	 * <p> 
	 * <ul>
	 * <li> <strong>latlon[0]:</strong> The latitude in WGS84 format.
	 * <li> <strong>latlon[1]:</strong> The longitude in WGS84 format.
	 * </ul>
	 * 
	 * @see {@link WayPoint#WayPoint(String, double[])}
	 */
	public void setNextDestination(String destinationName, double[] latlon){
		this.mDestination = new WayPoint(destinationName, latlon);
		this.onResume();
	}

	/**
	 *  Set the next destination. This is the point to where the user will be guided to
	 * @param destination A WayPoint
	 * 
	 * @throws NullPointerException
	 * @see {@link WayPoint}
	 */
	public void setNextDestination(WayPoint waypoint){
		if(waypoint == null){
			throw new NullPointerException("waypoint");
		}
		this.mDestination = waypoint;
		this.onResume();
		if(DEBUG) Log.d(TAG, waypoint.toString());
	}

	/**
	 * The current rate interval value in millis between interactions can be changed if the 
	 * {@link #DISTANCE_FEEDBACK_ENABLED} is disabled.
	 * 
	 * <p>If the {@link #DISTANCE_FEEDBACK_ENABLED} is enabled, no changes will apply to the 
	 * interaction.
	 * 
	 * @param rateInterval The value in millis for recurrence of vibration.
	 * @return whether it was successfully set
	 * 
	 * @throws IllegalArgumentException if the value is lower than 470 ms.
	 */
	public boolean setRateInterval(int rateInterval){		
		if(isDistanceFeedbackEnabled()){
			Log.w(TAG, "Distance feedback is enabled. Unable to set the rate interval.");
			return false;
		}

		if(rateInterval < MIN_PATTERN_LENGTH_TIME){
			throw new IllegalArgumentException("The value " + rateInterval + " should be larger than " + MIN_PATTERN_LENGTH_TIME);
		} else {
			this.mCustomPostDelayValue = rateInterval;
			this.mHasCustomPostDelayValue = true;
			return true;
		}
	}

	/**
	 * Sets the default rate interval value.
	 * @see #setRateInterval(int)
	 */
	public void setDefaultRateInterval(){
		this.mHasCustomPostDelayValue = false;
		this.mCustomPostDelayValue = DEFAULT_VIBRATION_RATE_INTERVAL;
	}

	/**
	 * Private method used to update the current values of this module. The method is
	 * run from the {@link Runnable} {@link #vibrate} which is updated using a fixed or
	 * generated time value. 
	 *
	 * @return whether the calculation was completed or an exception was found.
	 * 
	 * @see {@link #getVibrationRate()}
	 * @see #DEFAULT_DISTANCE_LIMIT
	 */
	private synchronized boolean updateValues(){
		boolean ans = false;
		if(isHaptiMapToolkitEnabled()){
			updateMyHaptiMapLocation();
		}
		if(mCurrentLocation != null && this.mDestination != null){
			Location destLoc = this.mDestination.getLocation();
			try{
				this.mDistanceToDestination = Math.round(mCurrentLocation.distanceTo(destLoc));
				this.mNextDirection = mCurrentLocation.bearingTo(destLoc);
				this.mDeviation = calculateDeviation();
				if(DEBUG) Log.e(TAG, "initial bearing(mNextDirection)=" + mNextDirection + "mAzimuth=" + mAzimuth + "mDeviation=" + mDeviation + " mDistanceToDestination=" + mDistanceToDestination);
				ans = true;
			} catch (Exception exc){
				ans = false;
			} 
		}
		return ans;
	}	

	//Starts the handler and post the vibrate runnable
	private void startHandler() {
		if(DEBUG) Log.e(TAG, "initHandler");
		if(mHandler != null && mDestination != null){
			if(!vibrationRunnerStarted){
				mHandler.post(vibrate);
				vibrationRunnerStarted = true;
			}
			else{
				if(DEBUG) Log.e(TAG, "handler already started...");
			}
		} else {
			mHandler = new Handler();
			mHandler.post(vibrate);
			vibrationRunnerStarted = true;
		}		
	}

	//Pauses the handler and removes callbacks
	private void pauseHandler(){
		if(mHandler != null){
			mHandler.removeCallbacks(vibrate);
			vibrationRunnerStarted = false;
		}
	}

	/**
	 * Resumes the handler. There could be an exception if the handler is not started with the
	 * {@link #startHandler()} and the {@link #mDestination} is not set. Then the handler should not
	 * be resumed, but it should be started. 
	 */
	private void resumeHandler(){
		if(mHandler != null && mDestination != null){
			if(!vibrationRunnerStarted){
				mHandler.post(vibrate);
				vibrationRunnerStarted = true;
			} else {
				mHandler.removeCallbacks(vibrate);
				mHandler.post(vibrate);
				vibrationRunnerStarted = true;
			}
		} else {
			this.startHandler();
			// An agreement on this issue should be discussed. Therefore the code row is commented below.
			//throw new IllegalStateException("wrong state!");
		}
	}

	//Stops the handler and removes vibration callbacks  
	private void stopHandler() {
		if(mHandler != null){
			mHandler.removeCallbacks(vibrate);
			vibrationRunnerStarted = false;
		}
		mHandler = null;
	}
	
	/**
	 * Check the status of the current interaction
	 * 
	 * @return whether the module is running
	 */
	public boolean isRunning(){
		return new Boolean(vibrationRunnerStarted);
	}

	private Runnable vibrate = new Runnable() {

		public void run() {
			if(updateValues()){
				if(outsidePoiRadius()){
					vibrate(isDistanceFeedbackEnabled() ? getPattern(mDeviation, mDistanceToDestination) : getPattern(mDeviation));
				}else{
					onDestinationReached();
					return;
				}
			}
			mHandler.postDelayed(vibrate, getVibrationRate());
		}
	};

	/**
	 * Check if the current distance to destination is valid according to {@link #POI_RADIUS}
	 * @return whether the distance is &gt {@link #POI_RADIUS}
	 */
	private boolean outsidePoiRadius(){
		return (mDistanceToDestination > POI_RADIUS);
	}

	/**
	 * Calculates the final deviation based on where the user is pointing at and where the 
	 * POI is located in reference to the user's current position.
	 * 
	 * @return the deviation calculated in degrees (0 to 359).
	 */
	private synchronized float calculateDeviation(){
		float ans = 0f;
		if (mNextDirection > mAzimuth) {
			ans = (mNextDirection - mAzimuth);
		}else {
			ans = (mNextDirection + 360 - mAzimuth);
		}
		ans = (360-ans)%360;
		return ans;
	}

	/**
	 * Simplified method that returns a pattern depending on the deviation. This method is used if 
	 * {@link #DISTANCE_FEEDBACK_ENABLED} is disabled. The occurrence of this event depends if the 
	 * user has modified the interaction rate or not.
	 * 
	 * @param deviation The deviation to the target from the users position.
	 * 
	 * @return The pattern that can be used on the {@link Vibrator}
	 * 
	 * @see #setDistanceFeedbackEnabled(boolean)
	 * @see #setRateInterval(int)
	 */
	public synchronized long[] getPattern(float deviation){
		switch(getCurrentState(deviation)){

		case AHEAD_ZONE_STATE:
			return AHEAD_PATTERN;

		case TURN_AROUND_STATE:
			return ZERO;

		default:
			return ZERO;
		}
	}

	/**
	 * The pattern created depends on the deviation and the distance to the POI from the user's current 
	 * location. This method is used if the {@link #DISTANCE_FEEDBACK_ENABLED} is enabled (which is the 
	 * default when creating this class).
	 *  
	 * @param deviation The deviation to the POI from the user's current location
	 * @param distance The distance in meters to the POI
	 * @return An array containing the pattern that could be used by the {@link Vibrator} 
	 */
	public synchronized long[] getPattern(float deviation, int distance) {
		mCurrentState = getCurrentState(deviation);

		if(mCurrentState == mPrevState && mCurrentPeriod > DEFAULT_VIBRATION_RATE_INTERVAL){
			mPattern = ZERO;
		} else if((mPrevState == AHEAD_ZONE_STATE) && !mAhead){
			mPattern = ZERO;
		} else {
			mPatternLength = MIN_PATTERN_LENGTH_TIME;

			switch(mCurrentState){

			case AHEAD_ZONE_STATE:
				mPatternLength = MIN_PATTERN_LENGTH_TIME;
				mPattern = AHEAD_PATTERN;
				break;

			case TURN_AROUND_STATE:
				mPatternLength = 0;
				mPattern = ZERO;
				break;

			default:
				break;
			}
		}

		if(distance > mCurrentDistanceLimit){
			distance = mCurrentDistanceLimit;
		}

		mCurrentPostDelayValue = createPeriod(distance);

		return mPattern;
	}

	/**
	 * The period is changed if the user moves the pointing device from target 
	 * or if the previously calculated period runs out. A new period is then 
	 * calculated with the new distance value and the current period counts down 
	 * until the procedure restarts again.
	 * 
	 * @param distance The distance to target
	 * @return Either the {@link #DEFAULT_VIBRATION_RATE_INTERVAL} or the residual of the
	 * current period.
	 */
	private synchronized int createPeriod(int distance){
		float tmpPeriod = DEFAULT_VIBRATION_RATE_INTERVAL;
		if(mCurrentState == mPrevState){
			if(mCurrentPeriod > DEFAULT_VIBRATION_RATE_INTERVAL){
				mCurrentPeriod -= DEFAULT_VIBRATION_RATE_INTERVAL;
			} else {				
				tmpPeriod = mCurrentPeriod;
				mCurrentPeriod = calculatePeriod(distance);
			}
		} else {
			mCurrentPeriod = calculatePeriod(distance);
		}
		return (int)tmpPeriod;
	}

	/**
	 * Calculates a period depending on the position to target. There are four different zones, which are:
	 * <ul>
	 * <li>{@link #RED_ZONE}</li>
	 * <li>{@link #YELLOW_ZONE}</li>
	 * <li>{@link #GREEN_ZONE}</li>
	 * <li>{@link #OUT_OF_BOUNDS_ZONE}</li>
	 * </ul>
	 * 
	 * Every zone represents a distance interval to a POI. 
	 * @param distance The current distance to a POI
	 * @return The final period depending on the distance to target. If the user is outside the {@link #OUT_OF_BOUNDS_ZONE} 
	 * the value will remain with a fix value: {@value#OUT_OF_RANGE_PERIOD} ms.
	 */
	private synchronized float calculatePeriod(int distance){
		float ans = DEFAULT_VIBRATION_RATE_INTERVAL;

		if(distance < RED_ZONE){
			ans = mPatternLength + Math.round((Math.log(distance))*(RED_ZONE_FACTOR + distance));
		} else if (distance < YELLOW_ZONE){
			ans = mPatternLength + Math.round((Math.log(distance))*(YELLOW_ZONE_FACTOR + distance));
		} else if (distance < GREEN_ZONE){
			ans = mPatternLength + Math.round((Math.log(distance))*(GREEN_ZONE_FACTOR + distance));
		} else {
			ans = distance < OUT_OF_BOUNDS_ZONE ? Math.round((Math.log(distance))*(OUT_OF_BOUNDS_ZONE_FACTOR + distance)) : OUT_OF_RANGE_PERIOD;
		}
		onRateIntervallChanged((int) ans);
		return ans; 
	}

	private synchronized int getCurrentState(float deviation){
		int state = 0;
		mPrevState = mCurrentState;
		mAhead = false;

		if(deviation <= MAX_AHEAD_DEVIATION || deviation >= (360-MAX_AHEAD_DEVIATION)){
			state = AHEAD_ZONE_STATE;
			mAhead = true;
		} else {	
			state = TURN_AROUND_STATE;
			mAhead = false;
		}
		return state;
	}

	/**
	 * The current vibration rate depending if the user has changed the default post delay. 
	 * The value is the recurrence of the interaction for the vibration thread. 
	 * 
	 * @return the current post delay value.
	 */
	public synchronized long getVibrationRate() {
		int ans;
		if(!mHasCustomPostDelayValue){
			ans = mCurrentPostDelayValue;
		} else {
			ans = mCustomPostDelayValue;
			onRateIntervallChanged(ans);
		}
		return ans;
		//		return mHasCustomPostDelayValue ? mCustomPostDelayValue : mCurrentPostDelayValue;
	}

	/**
	 * The method will prepare the feedback from the {@link HaptiMap} class.
	 * The current implementation does not work properly with the gps. The network string
	 * has to be provided in order to get it work.
	 * 
	 * @return whether there was a problem or not.
	 * 
	 * @see HaptiMap
	 */
	private boolean initHaptiMapToolkit(){
		boolean ans = false;
		int[] num_sensors = new int[1];
		if(mLocationSensorId == -1){
			if(mHaptiMap.LocationSensorInquire(num_sensors) != HaptiMap.SUCCESS) {
				Log.e(TAG, "Error querying location sensors");   
				ans = false;
				return ans;
			} else {
				ans = true;
				Log.i(TAG, "nr of sensors="+ num_sensors[0]);
			}
		} 

		int[] id = new int[1];
		int i;
		for(i = 0; i < num_sensors[0]; i++) {
			if(mHaptiMap.LocationSensorGetId(i, id) != HaptiMap.SUCCESS) {
				Log.e(TAG, "Error querying location sensor ID");
				continue;
			}
			StringBuilder str = new StringBuilder();
			if(mHaptiMap.LocationSensorGetAttributeString(id[0], HaptiMap.ATTRIBUTE.DEVICE_RELATIVE_NAME, str) != HaptiMap.SUCCESS) {
				Log.e(TAG, "Error querying sensor name "+id[0]);
				continue;
			}

			Log.i(TAG, "id[0]= " + id[0] + " >" + str.toString() + " i=" + i);

			if(str.toString().equals("network")) {
				mLocationSensorId = id[0];
				Log.e(TAG, "equals=" + str.toString());
				break;
			}
			str.append("Location Sensor "+i+"\tID "+id[0]+" Name: ");
			str.append("\n");
			Log.e(TAG, str.toString());
		}
		if(i == num_sensors[0]) {
			Log.e(TAG,"Unable to locate matching sensor");
			ans = false;
		}
		if(mHaptiMap.LocationSensorInitialise(mLocationSensorId) != HaptiMap.SUCCESS) {
			Log.e(TAG,"Unable to initialise location sensor");
			mLocationSensorId = -1;
			ans = false;
		} else {
			ans = true;
		}
		return ans;
	}

	//Does not work properly since i'm unable to let the HaptiMap class register the right location sensor..
	/**
	 * Updates the {@link #mCurrentLocation} with fresh values from the toolkit.
	 */
	private void updateMyHaptiMapLocation(){
		if(mHaptiMap != null){
			int[] is_3d = new int[1];
			if(mHaptiMap.LocationSensorGetLocationWGS84(this.mLocationSensorId, mHaptiMapLocation, is_3d) != HaptiMap.SUCCESS){
				Log.e(TAG, "There is an error in this method.");
			} else {
				if(DEBUG) Log.i(TAG, "location[0]=" + mHaptiMapLocation[0] + " location[1]=" + mHaptiMapLocation[1] + " location[2]=" + mHaptiMapLocation[2]);
				mCurrentLocation = new Location(WayPoint.PROVIDER);
				mCurrentLocation.setLatitude(mHaptiMapLocation[0]);
				mCurrentLocation.setLongitude(mHaptiMapLocation[1]);
			}
		}
	}

}
